#include <lpc17xx_ssp.h>
#include <lpc17xx_gpio.h>
#include <lpc17xx_gpdma.h>
#include <lpc17xx_pinsel.h>
#include <stdio.h>
#include <string.h>
#include "oled.h"
//OLED  SSP config
#define OLED_SSP_BASE       (LPC_SSP0)  //use SSP0 for oled
#define OLED_SSP_PORT       (0)         //OLED SSP port
#define OLED_SSP_MOSI       (18)        //SSP MOSI
#define OLED_SSP_MISO       (17)        //SSP MISO
#define OLED_SSP_SCLK       (15)        //SSP SCLK
#define OLED_SSP_SSEL       (16)        //SSP SCLK
#define OLED_SSP_CLCK       (125000000) //12.5Mhz
//OLED GPIO config
#define OLED_RS_PINNUM      (26)        //reset pin active low p17
#define OLED_DC_PINNUM      (25)        //data/command low for command p18
//OLED commands
#define OLED_SET_ROW_ADR    (0x75)      //set row start address
#define OLED_SET_COL_ADR    (0x15)      //set col start address
#define OLED_SRAM_WR_ENA    (0x5C)      //enable write to sram
#define OLED_SRAM_RD_ENA    (0x5D)      //enable read from sram
#define OLED_REMAP_DEPTH    (0xA0)      //re-map/color depth
#define OLED_DSPLY_START    (0xA1)      //display offset
#define OLED_DSPLY_ALLOFF   (0xA4)      //display all off
#define OLED_DSPLY_ALLON    (0xA5)      //display all on
#define OLED_DSPLY_NORMAL   (0xA6)      //display normal
#define OLED_DSPLY_INVERT   (0xA7)      //display inverted
#define OLED_DSPLY_ON       (0xAF)      //turn display on     
#define OLED_MASTER_CONF    (0xAD)      //master configuration
//OLED Display Dimensions
#define OLED_DSPLY_WIDTH    (128 )      //display width
#define OLED_DSPLY_HEIGHT   (128 )      //display height
#define OLED_DSPLY_DEPTH    (2   )      //display depth
#define OLED_FRAME_BUFFER   (8192)      //frame buffer size in words
#define OLED_DMA_CHANNEL    (0   )      //OLED GPDMA channel
#define OLED_DMA_TRANSFER   (2048)      //dma transfer size in words (maximum 11 bits)
#define GPIO_DIR_OUTPUT     (1   )      //GPIO output
#define MASK(P)             (1<<P)
static volatile uint32_t transfer_complete;
uint8_t frame_buffer[OLED_FRAME_BUFFER * 4] __attribute__ ((section (".ram2")));

//DMA configuration
static GPDMA_Channel_CFG_Type dma_cfg = {
    .DMALLI         = (uint32_t) NULL,
    .SrcMemAddr     = (uint32_t) NULL,
    .DstMemAddr     = (uint32_t) NULL,
    .SrcConn        = (uint32_t) NULL,
    .DstConn        = GPDMA_CONN_SSP0_Tx,
    .swidth         = GPDMA_WIDTH_WORD,
    .dwidth         = GPDMA_WIDTH_BYTE,
    .ChannelNum     = OLED_DMA_CHANNEL,
    .TransferSize   = OLED_DMA_TRANSFER,
    .TransferWidth  = 0,//only for M2M
    .TransferType   = GPDMA_TRANSFERTYPE_M2P,
};

void DMA_IRQHandler()
{
   if (GPDMA_IntGetStatus(GPDMA_STAT_INT, OLED_DMA_CHANNEL)) {
    GPDMA_ClearIntPending(GPDMA_STATCLR_INTTC, OLED_DMA_CHANNEL);
    ++transfer_complete;
   }
}

void oled_command(uint8_t c)
{
    while(SSP_GetStatus(OLED_SSP_BASE, SSP_STAT_BUSY));
    GPIO_ClearValue(OLED_SSP_PORT, MASK(OLED_DC_PINNUM));
    SSP_SendData(OLED_SSP_BASE, c);
}

void oled_data(uint8_t c)
{
    while(SSP_GetStatus(OLED_SSP_BASE, SSP_STAT_BUSY));
    GPIO_SetValue(OLED_SSP_PORT, MASK(OLED_DC_PINNUM));
    SSP_SendData(OLED_SSP_BASE, c);
}

void oled_init()
{
    PINSEL_CFG_Type pin_cfg={
        .Funcnum    = 2,
        .Portnum    = OLED_SSP_PORT,
        .Pinmode    = PINSEL_PINMODE_PULLUP,
        .OpenDrain  = PINSEL_PINMODE_NORMAL,
    };
    
    SSP_CFG_Type  ssp_cfg ={
        .CPHA           = SSP_CPHA_FIRST,
        .CPOL           = SSP_CPOL_HI,
        .ClockRate      = OLED_SSP_CLCK,
        .Databit        = SSP_DATABIT_8,
        .Mode           = SSP_MASTER_MODE,
        .FrameFormat    = SSP_FRAME_SPI
    };

    //SSP PINSEL configuration
    pin_cfg.Pinnum = OLED_SSP_SCLK;
    PINSEL_ConfigPin(&pin_cfg);

    pin_cfg.Pinnum = OLED_SSP_SSEL;
    PINSEL_ConfigPin(&pin_cfg);

    pin_cfg.Pinnum = OLED_SSP_MISO;
    PINSEL_ConfigPin(&pin_cfg);

    pin_cfg.Pinnum = OLED_SSP_MOSI;
    PINSEL_ConfigPin(&pin_cfg);

    //OLED DC/RS configuration
    pin_cfg.Funcnum = 0;
    pin_cfg.Pinnum = OLED_RS_PINNUM;
    PINSEL_ConfigPin(&pin_cfg);

    pin_cfg.Pinnum  = OLED_DC_PINNUM;
    PINSEL_ConfigPin(&pin_cfg);

    //set direction of GPIO RS   
    GPIO_SetDir(OLED_SSP_PORT, MASK(OLED_RS_PINNUM), GPIO_DIR_OUTPUT);
    GPIO_ClearValue(OLED_SSP_PORT, MASK(OLED_RS_PINNUM));

    //set direction of GPIO DC
    GPIO_SetDir(OLED_SSP_PORT, MASK(OLED_DC_PINNUM), GPIO_DIR_OUTPUT);
    GPIO_ClearValue(OLED_SSP_PORT, MASK(OLED_DC_PINNUM));

    //initialize SSP
    SSP_Init(OLED_SSP_BASE, &ssp_cfg);
    SSP_Cmd(OLED_SSP_BASE, ENABLE);
    SSP_DMACmd(OLED_SSP_BASE, SSP_DMA_TX, ENABLE);

    //initialize DMA
    GPDMA_Init();
    NVIC_DisableIRQ(DMA_IRQn);
    NVIC_SetPriority(DMA_IRQn, ((0x01<<3)|0x00));
    NVIC_EnableIRQ(DMA_IRQn);

    //initialize oled
    oled_reset();

    oled_command(OLED_REMAP_DEPTH); //set re-map/color depth
    oled_data(0x73);                    
   
    oled_command(OLED_MASTER_CONF); //set Master Configuration
    oled_data(0x8e);                    
   
    oled_command(OLED_DSPLY_START); //set display start line
    oled_data(0x20);                    
  
    oled_command(OLED_DSPLY_ON);    //Display on
}

void oled_reset()
{
    GPIO_ClearValue(OLED_SSP_PORT, MASK(OLED_RS_PINNUM));
    sleep(100);
    GPIO_SetValue(OLED_SSP_PORT, MASK(OLED_RS_PINNUM));
    sleep(100);
}

void oled_sram_mode()
{
    //Anything sent to oled after this command is
    //interpreted as data until the next command.
    oled_command(OLED_SRAM_WR_ENA);
    GPIO_SetValue(OLED_SSP_PORT, MASK(OLED_DC_PINNUM));
}

void oled_flush_buffer()
{
    uint32_t i;
 
    //switch to data mode
    oled_sram_mode();

    transfer_complete = 1;
    for (i = 0; i < OLED_FRAME_BUFFER; i += OLED_DMA_TRANSFER) {
        --transfer_complete;
        dma_cfg.SrcMemAddr = frame_buffer + i * 4;
        GPDMA_Setup(&dma_cfg);
        GPDMA_ChannelCmd(OLED_DMA_CHANNEL, ENABLE);
        while(!transfer_complete); //wait for transfer to complete
    }
}

uint8_t *oled_frame_buffer()
{
    return frame_buffer;
}

void oled_clear(uint16_t c)
{
    uint32_t size = (OLED_DSPLY_WIDTH  * OLED_DSPLY_HEIGHT);
    oled_command(OLED_SRAM_WR_ENA);
    while (size--) {
        oled_data((uint8_t)(c >> 8));
        oled_data((uint8_t)(c & 0x00FF));
    }
}

void oled_set_display(uint32_t x_off, uint32_t y_off, uint32_t width, uint32_t height)
{
    oled_command(OLED_SET_ROW_ADR);
    oled_data(x_off);
    oled_data(x_off + height-1);

    oled_command(OLED_SET_COL_ADR);
    oled_data(y_off);
    oled_data(y_off + width -1);
}


